#include "../include/Operation.h"

static const Complex cZERO(0.0);
static const Complex cONE(1.0);

// Addition
static Complex add_proc(const OperationData::ArgumentList&);
// Subtraction
static Complex subtract_proc(const OperationData::ArgumentList&);
// Negation
static Complex negate_proc(const OperationData::ArgumentList&);
// Multiplication
static Complex multiply_proc(const OperationData::ArgumentList&);
// Division
static Complex divide_proc(const OperationData::ArgumentList&);
// Inversion
static Complex inverse_proc(const OperationData::ArgumentList&);
// Exponentiation
static Complex pow_proc(const OperationData::ArgumentList&);
// Absolute value
static Complex abs_proc(const OperationData::ArgumentList&);
// Argument
static Complex arg_proc(const OperationData::ArgumentList&);
// Real part
static Complex real_proc(const OperationData::ArgumentList&);
// Imaginary part
static Complex imag_proc(const OperationData::ArgumentList&);
// Square root
static Complex sqrt_proc(const OperationData::ArgumentList&);
// Natural logarithm
static Complex ln_proc(const OperationData::ArgumentList&);
// Exponent of e
static Complex exp_proc(const OperationData::ArgumentList&);
// Base 10 logarithm
static Complex log10_proc(const OperationData::ArgumentList&);
// Base x logarithm
static Complex logx_proc(const OperationData::ArgumentList&);

////////////////////////////////////////////////////////////////////////////////
// ADDITION
////////////////////////////////////////////////////////////////////////////////
static const QString add_id("+");
static const QString add_hs( \
"NAME:\n"
"+ (Addition).\n\n"
"USAGE:\n"
"a+b.\n\n"
"DESCRIPTION:\n"
"Returns the sum of a and b.\n"
);
static const bool add_fn = false;
static const uint add_pr = 1;
static const uint add_na = 2;

static Complex add_proc(const OperationData::ArgumentList& args)
{
    if (static_cast<uint>(args.size()) != add_na) {
        throw Operation::Exception(
          QString("add() called with %1 arguments instead of %2.").
          arg(args.size()).arg(add_na));
    }
    return args.at(0) + args.at(1);
}

////////////////////////////////////////////////////////////////////////////////
// SUBTRACTION
////////////////////////////////////////////////////////////////////////////////
static const QString subtract_id("-");
static const QString subtract_hs( \
"NAME:\n"
"- (Subtraction).\n\n"
"USAGE:\n"
"a-b.\n\n"
"DESCRIPTION:\n"
"Returns a subtracted by b.\n"
);
static const bool subtract_fn = false;
static const uint subtract_pr = 1;
static const uint subtract_na = 2;

static Complex subtract_proc(const OperationData::ArgumentList& args)
{
    if (static_cast<uint>(args.size()) != subtract_na) {
        throw Operation::Exception(
          QString("subtract() called with %1 arguments instead of %2.").
          arg(args.size()).arg(subtract_na));
    }
    return args.at(0) - args.at(1);
}

////////////////////////////////////////////////////////////////////////////////
// NEGATION
////////////////////////////////////////////////////////////////////////////////
static const QString negate_id("-");
static const QString negate_hs( \
"NAME:\n"
"- (Negation).\n\n"
"USAGE:\n"
"-a.\n\n"
"DESCRIPTION:\n"
"Returns negative a.\n"
);
static const bool negate_fn = false;
static const uint negate_pr = 1;
static const uint negate_na = 1;

static Complex negate_proc(const OperationData::ArgumentList& args)
{
    if (static_cast<uint>(args.size()) != negate_na) {
        throw Operation::Exception(
          QString("negate() called with %1 arguments instead of %2.").
          arg(args.size()).arg(negate_na));
    }
    return -args.at(0);
}

////////////////////////////////////////////////////////////////////////////////
// MULTIPLICATION
////////////////////////////////////////////////////////////////////////////////
static const QString multiply_id("*");
static const QString multiply_hs( \
"NAME:\n"
"* (Multiplication).\n\n"
"USAGE:\n"
"a*b.\n\n"
"DESCRIPTION:\n"
"Returns a multiplyed by b.\n"
);
static const bool multiply_fn = false;
static const uint multiply_pr = 2;
static const uint multiply_na = 2;

static Complex multiply_proc(const OperationData::ArgumentList& args)
{
    if (static_cast<uint>(args.size()) != multiply_na) {
        throw Operation::Exception(
          QString("multiply() called with %1 arguments instead of %2.").
          arg(args.size()).arg(multiply_na));
    }
    return args.at(0) * args.at(1);
}

////////////////////////////////////////////////////////////////////////////////
// DIVISION
////////////////////////////////////////////////////////////////////////////////
static const QString divide_id("/");
static const QString divide_hs( \
"NAME:\n"
"/ (Division).\n\n"
"USAGE:\n"
"a/b.\n\n"
"DESCRIPTION:\n"
"Returns a divided by b.\n"
);
static const bool divide_fn = false;
static const uint divide_pr = 2;
static const uint divide_na = 2;

static Complex divide_proc(const OperationData::ArgumentList& args)
{
    if (static_cast<uint>(args.size()) != divide_na) {
        throw Operation::Exception(
          QString("divide() called with %1 arguments instead of %2.").
          arg(args.size()).arg(divide_na));
    }
    if (args.at(1) == cZERO) {
        throw Operation::Exception("divide(): Divide by zero.");
    }
    return args.at(0) / args.at(1);
}

////////////////////////////////////////////////////////////////////////////////
// INVERSION
////////////////////////////////////////////////////////////////////////////////
static const QString inverse_id("inv");
static const QString inverse_hs( \
"NAME:\n"
"inv.\n\n"
"USAGE:\n"
"inv(a).\n\n"
"DESCRIPTION:\n"
"Returns the inverse of a (1/a).\n"
);
static const bool inverse_fn = true;
static const uint inverse_pr = 10;
static const uint inverse_na = 1;

static Complex inverse_proc(const OperationData::ArgumentList& args)
{
    if (static_cast<uint>(args.size()) != inverse_na) {
        throw Operation::Exception(
          QString("inverse() called with %1 arguments instead of %2.").
          arg(args.size()).arg(inverse_na));
    }
    if (args.at(0) == cZERO) {
        throw Operation::Exception("inverse(): Inverse of 0.");
    }
    return inv(args.at(0));
}

////////////////////////////////////////////////////////////////////////////////
// EXPONENTIATION
////////////////////////////////////////////////////////////////////////////////
static const QString pow_id("^");
static const QString pow_hs( \
"NAME:\n"
"^ (To the power of).\n\n"
"USAGE:\n"
"a^b.\n\n"
"DESCRIPTION:\n"
"Returns a to the power of b.\n"
);
static const bool pow_fn = false;
static const uint pow_pr = 3;
static const uint pow_na = 2;

static Complex pow_proc(const OperationData::ArgumentList& args)
{
    if (static_cast<uint>(args.size()) != pow_na) {
        throw Operation::Exception(
          QString("pow() called with %1 arguments instead of %2.").
          arg(args.size()).arg(pow_na));
    }
    return pow(args.at(0), args.at(1));
}

////////////////////////////////////////////////////////////////////////////////
// ABSOLUTE VALUE
////////////////////////////////////////////////////////////////////////////////
static const QString abs_id("abs");
static const QString abs_hs( \
"NAME:\n"
"abs.\n\n"
"USAGE:\n"
"abs(a).\n\n"
"DESCRIPTION:\n"
"Returns the absolute value of a (|a|).\n"
);
static const bool abs_fn = true;
static const uint abs_pr = 10;
static const uint abs_na = 1;

static Complex abs_proc(const OperationData::ArgumentList& args)
{
    if (static_cast<uint>(args.size()) != abs_na) {
        throw Operation::Exception(
          QString("abs() called with %1 arguments instead of %2.").
          arg(args.size()).arg(abs_na));
    }
    return Complex(abs(args.at(0)));
}

////////////////////////////////////////////////////////////////////////////////
// ARGUMENT
////////////////////////////////////////////////////////////////////////////////
static const QString arg_id("arg");
static const QString arg_hs( \
"NAME:\n"
"arg.\n\n"
"USAGE:\n"
"arg(a).\n\n"
"DESCRIPTION:\n"
"Returns the angle a makes with the positive real axis on the cartesian "
"plane (in radians).\n"
);
static const bool arg_fn = true;
static const uint arg_pr = 10;
static const uint arg_na = 1;

static Complex arg_proc(const OperationData::ArgumentList& args)
{
    if (static_cast<uint>(args.size()) != arg_na) {
        throw Operation::Exception(
          QString("arg() called with %1 arguments instead of %2.").
          arg(args.size()).arg(arg_na));
    }
    if (args.at(0) == cZERO) {
        throw Operation::Exception("arg(): Argument of 0.");
    }
    return Complex(arg(args.at(0)));
}

////////////////////////////////////////////////////////////////////////////////
// REAL PART
////////////////////////////////////////////////////////////////////////////////
static const QString real_id("real");
static const QString real_hs( \
"NAME:\n"
"real.\n\n"
"USAGE:\n"
"real(a).\n\n"
"DESCRIPTION:\n"
"Returns the real part of a.\n"
);
static const bool real_fn = true;
static const uint real_pr = 10;
static const uint real_na = 1;

static Complex real_proc(const OperationData::ArgumentList& args)
{
    if (static_cast<uint>(args.size()) != real_na) {
        throw Operation::Exception(
          QString("real() called with %1 arguments instead of %2.").
          arg(args.size()).arg(real_na));
    }
    return Complex(real(args.at(0)));
}

////////////////////////////////////////////////////////////////////////////////
// IMAGINARY PART
////////////////////////////////////////////////////////////////////////////////
static const QString imag_id("imag");
static const QString imag_hs( \
"NAME:\n"
"imag.\n\n"
"USAGE:\n"
"imag(a).\n\n"
"DESCRIPTION:\n"
"Returns the imaginary part of a.\n"
);
static const bool imag_fn = true;
static const uint imag_pr = 10;
static const uint imag_na = 1;

static Complex imag_proc(const OperationData::ArgumentList& args)
{
    if (static_cast<uint>(args.size()) != imag_na) {
        throw Operation::Exception(
          QString("imag() called with %1 arguments instead of %2.").
          arg(args.size()).arg(imag_na));
    }
    return Complex(imag(args.at(0)));
}

////////////////////////////////////////////////////////////////////////////////
// SQUARE ROOT
////////////////////////////////////////////////////////////////////////////////
static const QString sqrt_id("sqrt");
static const QString sqrt_hs( \
"NAME:\n"
"sqrt.\n\n"
"USAGE:\n"
"sqrt(a).\n\n"
"DESCRIPTION:\n"
"Returns the square root of a.\n"
);
static const bool sqrt_fn = true;
static const uint sqrt_pr = 10;
static const uint sqrt_na = 1;

static Complex sqrt_proc(const OperationData::ArgumentList& args)
{
    if (static_cast<uint>(args.size()) != sqrt_na) {
        throw Operation::Exception(
          QString("sqrt() called with %1 arguments instead of %2.").
          arg(args.size()).arg(sqrt_na));
    }
    return sqrt(args.at(0));
}

////////////////////////////////////////////////////////////////////////////////
// NATURAL LOGARITHM
////////////////////////////////////////////////////////////////////////////////
static const QString ln_id("ln");
static const QString ln_hs( \
"NAME:\n"
"ln.\n\n"
"USAGE:\n"
"ln(a).\n\n"
"DESCRIPTION:\n"
"Returns the natural logarithm of a.\n"
);
static const bool ln_fn = true;
static const uint ln_pr = 10;
static const uint ln_na = 1;

static Complex ln_proc(const OperationData::ArgumentList& args)
{
    if (static_cast<uint>(args.size()) != ln_na) {
        throw Operation::Exception(
          QString("ln() called with %1 arguments instead of %2.").
          arg(args.size()).arg(ln_na));
    }
    if (args.at(0) == cZERO) {
        throw Operation::Exception("ln(): Logarithm of 0.");
    }
    return ln(args.at(0));
}

////////////////////////////////////////////////////////////////////////////////
// EXPONENT OF E
////////////////////////////////////////////////////////////////////////////////
static const QString exp_id("exp");
static const QString exp_hs( \
"NAME:\n"
"exp.\n\n"
"USAGE:\n"
"exp(a).\n\n"
"DESCRIPTION:\n"
"Returns e^a, where e is Euler's number (approximately 2.71828).\n"
);
static const bool exp_fn = true;
static const uint exp_pr = 10;
static const uint exp_na = 1;

static Complex exp_proc(const OperationData::ArgumentList& args)
{
    if (static_cast<uint>(args.size()) != exp_na) {
        throw Operation::Exception(
          QString("exp() called with %1 arguments instead of %2.").
          arg(args.size()).arg(exp_na));
    }
    return exp(args.at(0));
}

////////////////////////////////////////////////////////////////////////////////
// BASE 10 LOGARITHM
////////////////////////////////////////////////////////////////////////////////
static const QString log10_id("log10");
static const QString log10_hs( \
"NAME:\n"
"log10.\n\n"
"USAGE:\n"
"log10(a).\n\n"
"DESCRIPTION:\n"
"Returns the base 10 logarithm of a.\n"
);
static const bool log10_fn = true;
static const uint log10_pr = 10;
static const uint log10_na = 1;

static Complex log10_proc(const OperationData::ArgumentList& args)
{
    if (static_cast<uint>(args.size()) != log10_na) {
        throw Operation::Exception(
          QString("log10() called with %1 arguments instead of %2.").
          arg(args.size()).arg(log10_na));
    }
    if (args.at(0) == cZERO) {
        throw Operation::Exception("log10(): Logarithm of 0.");
    }
    return log10(args.at(0));
}

////////////////////////////////////////////////////////////////////////////////
// BASE X LOGARITHM
////////////////////////////////////////////////////////////////////////////////
static const QString logx_id("logx");
static const QString logx_hs( \
"NAME:\n"
"logx.\n\n"
"USAGE:\n"
"logx(a, b).\n\n"
"DESCRIPTION:\n"
"Returns the base a logarithm of b.\n"
);
static const bool logx_fn = true;
static const uint logx_pr = 10;
static const uint logx_na = 2;

static Complex logx_proc(const OperationData::ArgumentList& args)
{
    if (static_cast<uint>(args.size()) != logx_na) {
        throw Operation::Exception(
          QString("logx() called with %1 arguments instead of %2.").
          arg(args.size()).arg(logx_na));
    }
    if (args.at(0) == cZERO) {
        throw Operation::Exception("logx(): Logarithm base 0.");
    } else if (args.at(0) == cONE) {
        throw Operation::Exception("logx(): Logarithm base 1.");
    } else if (args.at(1) == cZERO) {
        throw Operation::Exception("logx(): Logarithm of 0.");
    }
    return logx(args.at(0), args.at(1));
}

////////////////////////////////////////////////////////////////////////////////
// Add to operations list.
////////////////////////////////////////////////////////////////////////////////
static const Operation oadd(add_id, add_hs, add_fn, add_pr, add_na, add_proc);
static const Operation osubtract(subtract_id, subtract_hs, subtract_fn,
                                 subtract_pr, subtract_na, subtract_proc);
static const Operation onegate(negate_id, negate_hs, negate_fn, negate_pr,
                               negate_na, negate_proc);
static const Operation omultiply(multiply_id, multiply_hs, multiply_fn,
                                 multiply_pr, multiply_na, multiply_proc);
static const Operation odivide(divide_id, divide_hs, divide_fn, divide_pr,
                               divide_na, divide_proc);
static const Operation oinverse(inverse_id, inverse_hs, inverse_fn, inverse_pr,
                                inverse_na, inverse_proc);
static const Operation opow(pow_id, pow_hs, pow_fn, pow_pr, pow_na, pow_proc);
static const Operation oabs(abs_id, abs_hs, abs_fn, abs_pr, abs_na, abs_proc);
static const Operation oarg(arg_id, arg_hs, arg_fn, arg_pr, arg_na, arg_proc);
static const Operation oreal(real_id, real_hs, real_fn, real_pr, real_na,
                             real_proc);
static const Operation oimag(imag_id, imag_hs, imag_fn, imag_pr, imag_na,
                             imag_proc);
static const Operation osqrt(sqrt_id, sqrt_hs, sqrt_fn, sqrt_pr, sqrt_na,
                             sqrt_proc);
static const Operation oln(ln_id, ln_hs, ln_fn, ln_pr, ln_na, ln_proc);
static const Operation oexp(exp_id, exp_hs, exp_fn, exp_pr, exp_na, exp_proc);
static const Operation olog10(log10_id, log10_hs, log10_fn, log10_pr, log10_na,
                             log10_proc);
static const Operation ologx(logx_id, logx_hs, logx_fn, logx_pr, logx_na,
                             logx_proc);
